import { app } from "../../base/typings/global";
import { ComponentClass, IComponent, ComponentMate, Component } from "./Component";
import { EntityID } from "./Entity";
import { Mask } from "./Mask";
import { System, SystemClass } from "./System";


export type ComponentType = number;
type ComponentInfo = {
    type: ComponentType,
    name: string
}
/**
 * 所有ECS的组件注册信息
 */
const _componentClassMap: Map<ComponentClass, ComponentInfo> = new Map();

/**
 * 注册组件
 * @returns 
 */
export function component(name?: string): any {
    return function (target: ComponentClass, propertyKey: string) {
        _componentClassMap.set(target, { type: _componentClassMap.size, name: name || target['name'] });
    }
}

function getComponentInfo(componentClass: any): ComponentInfo {
    return _componentClassMap.get(componentClass)!;
}


const _maskPool: Mask[] = [];
/**
 * 创建掩码
 * @param components 
 * @returns 
 */
function createMask(...components: ComponentClass[]): Mask {
    let mask = _maskPool.pop() || new Mask();
    for (const clazz of components) {
        mask.set(getComponentInfo(clazz).type);
    }
    return mask;
}

/**
 * 释放掩码
 * @param mask 
 */
function releaseMask(mask:Mask):void {
    mask.clear()
    _maskPool.push(mask);
}



/**
 * ECS
 */
export class ECS {
    private _name: string;
    private _running: boolean;
    private _frameCount: number = 0;
    private _entityCount: number = 0;
    private _systemCount: number = 0;
    private _entities: Map<EntityID, Mask> = new Map();
    private _components: Map<ComponentType, Map<EntityID, IComponent>> = new Map();
    private _systems: System[] = [];
    public get name(): string {
        return this._name;
    }

    /**
     * 是否在运行中
     */
    public get isRunning(): boolean {
        return this._running;
    }

    constructor(name: string) {
        this._name = name;
    }

    /**
     * 开始ECS的主循环
     */
    public launch(): void {
        if (this.isRunning) {
            throw new Error("ECS is running, can not start again.");
        }
        this._running = true;
        for (let i = 0; i < this._systems.length; i++) {
            const system = this._systems[i];
            system.start();
        }
        app.timer.frameLoop(1, this, this._tick);
    }

    private _tick(deltaTime: number): void {
        this._frameCount++;
        for (let i = 0; i < this._systemCount; i++) {
            const system = this._systems[i];
            if (system.isRunning && this._frameCount % system.frequency == 0 && system.condition(this._frameCount)) {
                system.onUpdate(deltaTime);
            }
        }
    }

    /**
     * 停止ECS
     */
    public shutdown(): void {
        app.timer.clear(this, this._tick);
        for (const system of this._systems) {
            system.stop();
        }
        this._running = false;
    }

    /**
     * 停止并重置ECS
     */
    public reset(): void {
        this.shutdown();
        this._frameCount = 0;
        this._entityCount = 0;
        this._systemCount = 0;
        this._entities.forEach((value, key)=>{
            this.releaseEntity(key);
        });
        this._entities.clear();
        this._components.clear();
        this._systems.length = 0;
    }

    private _getComponentMapOfType<T extends IComponent>(type: ComponentType): Map<EntityID, T> {
        if (this._components.has(type)) {
            return <Map<EntityID, T>>this._components.get(type)!;
        }
        let result = new Map<EntityID, T>();
        this._components.set(type, result)
        return result;
    }

    /**
     * 创建实体
     * @param components 实体所附带的若干组件
     * @returns 返回实体ID
     */
    public createEntity(...components: ComponentMate[]): EntityID;
    /**
     * 创建实体
     * @param components 
     */
    public createEntity(...components: ComponentMate[]): EntityID;
    public createEntity(...components: ComponentMate[]): EntityID {
        let entityId: EntityID = ++this._entityCount;
        this._entities.set(entityId, createMask());
        components.length && this.addComponents(entityId, ...components);
        return entityId;
    }

    /**
     * 释放实体,同时会销毁相关Component
     * @param entityId 实体ID
     * @returns 
     */
    public releaseEntity(entityId: EntityID): boolean {
        let entity = this._entities.get(entityId);
        if (entity) {
            this._components.forEach((map, key) => {
                if(map.has(entityId)){
                    let component = this.removeComponent(map.get(entityId));
                    component.release();
                }
            });
            releaseMask(entity);
            this._entities.delete(entityId);
            return true;
        }
        return false;
    }

    /**
    * 通过掩码查询实体
    * @param mask 
    */
    public getEntities(mask: Mask): EntityID[];
    /**
    * 通过组件匹配查询实体
    * @param components 
    * @returns 
    */
    public getEntities(component:{new():IComponent}, ...components: {new():IComponent}[]): EntityID[];
    public getEntities(...components: any[]): EntityID[] {
        let result: EntityID[] = [];
        if (components.length == 1 && components[0] instanceof Mask) {
            this._entities.forEach((entity, id) => {
                entity.has(<Mask>components[0]) && result.push(id);
            })
        } else {
            let mask: Mask = createMask(...components);
            this._entities.forEach((entity, id) => {
                entity.has(mask) && result.push(id);
            })
            releaseMask(mask);
        }
        return result;
    }

    /**
     * 添加组件到实体
     * @param entityId 实体ID
     * @param component 组件Class
     * @param data 若干个组件数据
     */
    public addComponents<T extends Component>(entityId: EntityID, component: ComponentMate): T | null;
    /**
     * 添加组件到实体
     * @param entityId 实体ID
     * @param components 一个或多个组件
     */
    public addComponents(entityId: EntityID, ...components: ComponentMate[]): Component[] | null;
    public addComponents(entityId: EntityID, ...components: ComponentMate[]): Component | (Component | null)[] | null {
        if (components.length == 0) {
            return null;
        }
        console.log("添加组件：", entityId, ...components.map((value)=>{
            return getComponentInfo(value['clazz'] || value).name
        }))
        if (components.length == 1) {
            let component = components[0];
            let data = component['data'] || [];
            return this._addComponent(entityId, component['clazz'] || component, ...data);
        }
        let result: (Component | null)[] = [];
        for (const component of components) {
            let data = component['data'] || [];
            result.push(this._addComponent(entityId, component['clazz'] || component, ...data));
        }
        return result;
    }

    /**
     * 添加组件到实体
     * @param entityId 实体ID
     * @param clazz 组件class
     * @param datas 组件数据
     * @returns 
     */
    private _addComponent<T extends Component>(entityId: EntityID, clazz: ComponentClass, ...datas:any[]): T | null {
        if (this.containComponent(entityId, clazz)) {
            return null;
        }
        let component = clazz.create();
        this.addComponent(entityId, component, ...datas);
        return component as T;
    }

    /**
     * 添加组件对象到实体,可用于外部引擎自动实例化的组件.
     * @param entityId 实体ID
     * @param component 组件对象
     * @param datas 传到组件onCreate函数的的参数列表
     */
    public addComponent(entityId:EntityID, component:IComponent, ...datas:any[]):void {
        let entity = this._entities.get(entityId)!;
        let info = getComponentInfo(component.constructor);
        if(entity.has(info.type)){
            throw new Error(`Type of ${info.type} has existed in Entity:${entityId}`);
        }
        if(entity.has(info.type)){
            throw new Error(`Type of ${info.type} has existed in Entity:${entityId}`);
        }
        if(info){
            component.onCreate(info.type, info.name, ...datas);
        }
        this._getComponentMapOfType(info.type).set(entityId, component);
        entity.set(info.type);
        component.onAdd(entityId);
    }

    /**
     * 移除实体的组件
     * @param component 组件对象
     */
        public removeComponent(component:IComponent): IComponent;
        /**
         * 批量移除实体的组件
         * @param components 若干个组件对象
         */
        public removeComponent(...components:IComponent[]): IComponent[];
        public removeComponent(...components:IComponent[]): IComponent[]|IComponent {
        let result = [];
        
        console.log("移除组件：", components[0].entity, ...components.map((value)=>{
            return value.name
        }))
        for (let i = 0; i < components.length; i++) {
            const component = components[i];   
            let entity = this._entities.get(component.entity)!;
            if (!entity) {
                throw new Error(`实体:${component.entity}不存在`);
            }
            let componentSet = this._getComponentMapOfType(component.type);
            componentSet.delete(component.entity);
            entity.unset(component.type);
            component.onRemove();
            result.push(component);
        }
        return result.length == 1 ? result[0] : result;
    }

    /**
     * 移除实体的组件
     * @param entityId 组件ID
     * @param componentClass 组件class
     */
    public removeComponentWithClass(entityId: EntityID, componentClass: ComponentClass): IComponent;
    /**
     * 批量移除实体的组件
     * @param entityId 组件ID
     * @param componentClasses 组件Class列表
     */
    public removeComponentWithClass(entityId: EntityID, ...componentClasses: ComponentClass[]): IComponent[];
    public removeComponentWithClass(entityId: EntityID, ...componentClasses: ComponentClass[]): IComponent[]|IComponent {
        let components = componentClasses.map((clazz, key)=>{
            return this.getComponent(entityId, clazz);
        }).filter(value=>value);
        return components.length && this.removeComponent(...components);
    }

    /**
     * 实体是否包含组件mask
     * @param mask 组件mask
     */
    public containComponent(entityId: EntityID, mask: Mask): boolean;
    /**
     * 实体是否包含组件
     * @param components 系列组件,全部包含才返回true.
     */
    public containComponent(entityId: EntityID, ...components: ComponentClass[]): boolean;
    public containComponent(entityId: EntityID, ...components: any[]): boolean {
        let entity = this._entities.get(entityId);
        if (!entity) {
            throw new Error(`实体:${entityId}不存在`);
        }
        if (components.length == 1) {
            if (components[0] instanceof Mask) {
                return entity.has(components[0]);
            } else {
                return entity.has(getComponentInfo(components[0]).type);
            }
        }
        let mask: Mask = createMask(...components);
        let result: boolean = entity.has(mask);
        releaseMask(mask);
        return result;
    }

    /**
     * 获取实体组件
     * @param componentClass 组件class
     * @returns `T` or `null`
     */
    public getComponent<T extends IComponent>(entityId: EntityID, componentClass: ComponentClass | {new():IComponent}): T | null {
        if (!this._entities.has(entityId)) {
            throw new Error(`实体:${entityId}不存在`);
        }
        let type = getComponentInfo(componentClass).type;
        let result = this._getComponentMapOfType<T>(type).get(entityId);
        return result;
    }

    /**
     * 获取指定类型的所有组件
     * @param type 
     * @returns 
     */
    public getComponents<T extends IComponent>(componentClass: ComponentClass | {new():IComponent}):T[] {
        let type = getComponentInfo(componentClass).type;
        return Array.from(this._getComponentMapOfType<T>(type).values());
    }

    /**
     * 添加系统
     * @param systems 若干系统Class
     */
    public addSystem(...systems: SystemClass[]): void {
        for (const clazz of systems) {
            let system = new clazz(this);
            this._systems.push(system);
            this._systemCount++;
        }
        this._systems.sort((a, b) => a.priority - b.priority);
    }

    /**
     * 移除系统
     * @param system 
     * @returns 
     */
    public removeSystem(system: System): boolean {
        let index = this._systems.indexOf(system);
        if (index == -1) {
            return false;
        }
        system.stop();
        this._systems.splice(index, 1);
        this._systemCount--;
        return true;
    }
}
